/*
 * Copyright(C) 2020. Huawei Technologies Co.,Ltd. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "QualityEvaluation.h"

#include <cmath>
#include <memory>

#include "Log/Log.h"
#include "FileEx/FileEx.h"
#include "PointerDeleter/PointerDeleter.h"
#include "FaceBlockingMap.h"
#include "TestCV/TestCV.h"

namespace ascendFaceRecognition {
namespace {
const int FACE_KEY_POINT_SIZE = 5;
}

QualityEvaluation::QualityEvaluation()
{
    isStop_ = true;
    instanceId_ = -1;
}

QualityEvaluation::~QualityEvaluation()
{
}

APP_ERROR QualityEvaluation::ParseConfig(ConfigParser &configParser)
{
    LogDebug << "QualityEvaluation[" << instanceId_ << "]: begin to parse config values.";
    std::string itemCfgStr;

    std::vector<APP_ERROR> vecRet;
    const uint32_t maxCfgItemsNum = 32;
    uint32_t indexVec = 0;
    vecRet.resize(maxCfgItemsNum);

    itemCfgStr = moduleName_ + std::string(".keyPointWeight");
    vecRet[indexVec++] = configParser.GetFloatValue(itemCfgStr, keyPointWeight_);
    itemCfgStr = moduleName_ + std::string(".eulerWeight");
    vecRet[indexVec++] = configParser.GetFloatValue(itemCfgStr, eulerWeight_);
    itemCfgStr = moduleName_ + std::string(".bigFaceWeight");
    vecRet[indexVec++] = configParser.GetFloatValue(itemCfgStr, bigFaceWeight_);
    itemCfgStr = moduleName_ + std::string(".minOutputScore");
    vecRet[indexVec++] = configParser.GetFloatValue(itemCfgStr, minOutputScore_);
    itemCfgStr = moduleName_ + std::string(".maxSendTime");
    vecRet[indexVec++] = configParser.GetDoubleValue(itemCfgStr, maxSendTime_);

    for (uint32_t i = 0; i < indexVec; ++i) {
        if (vecRet[i] != APP_ERR_OK) {
            return vecRet[i];
        }
    }

    LogDebug << "QualityEvaluation[" << instanceId_ << "]: keyPointWeight_:" << keyPointWeight_ <<
             " eulerWeight_:" << eulerWeight_ << " bigFaceWeight_:" << bigFaceWeight_ << " minOutputScore_:"
             << minOutputScore_ << " maxSendTime_:" << maxSendTime_;

    return APP_ERR_OK;
}

APP_ERROR QualityEvaluation::Init(ConfigParser &configParser, ModuleInitArgs &initArgs)
{
    LogDebug << "QualityEvaluation[" << instanceId_ << "]: Begin to init quality evaluation instance" <<
             initArgs.instanceId;

    AssignInitArgs(initArgs);

    isStop_ = false;

    // initialize config params
    APP_ERROR ret = ParseConfig(configParser);
    if (ret != APP_ERR_OK) {
        LogFatal << "QualityEvaluation[" << instanceId_ << "]: Fail to parse config params, ret=" << ret << "(" <<
                 GetAppErrCodeInfo(ret) << ").";
        return ret;
    }

    sendThread_ = std::thread(&QualityEvaluation::SendThread, this);
    return APP_ERR_OK;
}

APP_ERROR QualityEvaluation::DeInit(void)
{
    LogDebug << "QualityEvaluation[" << instanceId_ << "]::begin to deinit.";

    sendStop_ = true;
    if (sendThread_.joinable()) {
        sendThread_.join();
    }

    return APP_ERR_OK;
}

APP_ERROR QualityEvaluation::FaceQualityEvaluation(const std::shared_ptr<FaceObject> &faceObject)
{
    float keyPointScore = CalKeyPointScore(faceObject);
    float eulerScore = CalEulerScore(faceObject);
    float bigFaceScore = CalBigFaceScore(faceObject);
    // wights in the configuration file, can be adjusted manually
    float score = keyPointWeight_ * keyPointScore + eulerWeight_ * eulerScore + bigFaceWeight_ * bigFaceScore;
    faceObject->faceQuality.score = score;
    LogDebug << "QualityEvaluation[" << instanceId_ << "]:"
             << "kpScore:" << keyPointScore << ", eulerScore:" << eulerScore << ", bigFaceScore:" << bigFaceScore;

    // filter
    if (score < minOutputScore_) {
        LogDebug << "QualityEvaluation[" << instanceId_ << "]: score is too low, score=" << score << ", kpScore:" <<
                 keyPointScore << ", eulerScore:" << eulerScore << ", bigFaceScore:" << bigFaceScore;
        return APP_ERR_OK;
    }
    auto &faceMap = FaceBlockingMap::GetInstance(faceObject->frameInfo.channelId);
    int32_t id = faceObject->trackInfo.id;
    std::shared_ptr<FaceObject> faceObjectCache = faceMap->Get(id);
    if (faceObjectCache == nullptr) {
        faceMap->Insert(id, faceObject);
    } else {
        if (faceObjectCache->faceQuality.score < faceObject->faceQuality.score) {
            faceMap->Insert(id, faceObject);
        }
    }
    return APP_ERR_OK;
}

float QualityEvaluation::CalKeyPointScore(const std::shared_ptr<FaceObject> &faceObject)
{
    float score = 0;
    if (faceObject->faceQuality.keyPoints.empty()) {
        LogWarn << "QualityEvaluation[" << instanceId_ << "]: keyPoints size is zero.";
        return score;
    }

    if (faceObject->faceQuality.keyPoints.size() != FACE_KEY_POINT_SIZE) {
        LogWarn << "QualityEvaluation[" << instanceId_ << "]: keyPoints size is invalid.";
        return score;
    }
    const float elementScoreLimit = 0.2;
    for (int i = 0; i < FACE_KEY_POINT_SIZE; ++i) {
        float tmpScore = faceObject->faceQuality.keyPoints[i];
        score += ((tmpScore > elementScoreLimit) ? elementScoreLimit : tmpScore);
    }
    return score;
}

float QualityEvaluation::CalEulerScore(const std::shared_ptr<FaceObject> &faceObject)
{
    const uint32_t degree90 = 90;
    float yaw = faceObject->faceQuality.angleYaw;
    float pitch = faceObject->faceQuality.anglePitch;
    float roll = faceObject->faceQuality.angleRoll;
    const uint32_t pitchConstant = 6;
    pitch = (pitch > pitchConstant) ? (pitch - pitchConstant) : 0;
    return (degree90 - yaw) / degree90 + (degree90 - pitch) / degree90 + (degree90 - roll) / degree90;
}

float QualityEvaluation::CalBigFaceScore(const std::shared_ptr<FaceObject> &faceObject)
{
    float width = faceObject->info.width;
    float height = faceObject->info.height;
    const uint16_t maxFaceHW = 60;
    const uint16_t normFaceHW = 50;
    const float faceStretchRatio = 1.2;
    const float faceScoreConstant = 3600.0;

    width = (width > normFaceHW) ? maxFaceHW : (width * faceStretchRatio);
    height = (height > normFaceHW) ? maxFaceHW : (height * faceStretchRatio);
    return 1 - fabsf(maxFaceHW - width) * fabsf(maxFaceHW - height) / faceScoreConstant;
}

APP_ERROR QualityEvaluation::Process(std::shared_ptr<void> inputData)
{
    LogDebug << "QualityEvaluation[" << instanceId_ << "]: Begin to process data.";
    std::shared_ptr<FaceObject> faceObject = std::static_pointer_cast<FaceObject>(inputData);

    if (faceObject == nullptr) {
        return APP_ERR_COMM_FAILURE;
    }

    using Time = std::chrono::high_resolution_clock;
    using Duration = std::chrono::duration<float>;
    using Second = std::chrono::duration<double, std::ratio<1, 1>>;
    auto currentTime = Time::now();

    // faceObject is ignored if its status is either lost or new
    // faceObject is ignored if exist time is greater than maxSendTime_
    Duration duration = currentTime - faceObject->trackInfo.createTime;
    double lastTime = std::chrono::duration_cast<Second>(duration).count();
    if (faceObject->trackInfo.flag == NEW_FACE || faceObject->trackInfo.flag == LOST_FACE || lastTime > maxSendTime_) {
        return APP_ERR_OK;
    }
    // get channels set
    channels_.insert(faceObject->frameInfo.channelId);
    FaceQualityEvaluation(faceObject);
    return APP_ERR_OK;
}

void QualityEvaluation::SendData()
{
    using Time = std::chrono::high_resolution_clock;
    using Duration = std::chrono::duration<float>;
    using Second = std::chrono::duration<double, std::ratio<1, 1>>;
    auto currentTime = Time::now();
    for (auto iter = channels_.begin(); iter != channels_.end(); iter++) {
        auto &faceMap = FaceBlockingMap::GetInstance(*iter);
        std::vector<int32_t> keys = faceMap->Keys();
        for (int32_t key : keys) {
            std::shared_ptr<FaceObject> face = faceMap->Get(key);
            if (face.get() == nullptr) {
                continue;
            }
            Duration duration = currentTime - face->trackInfo.createTime;
            double lastTime = std::chrono::duration_cast<Second>(duration).count();
            if (lastTime > maxSendTime_ || face->trackInfo.flag == LOST_FACE) {
                SendToNextModule(MT_WARP_AFFINE, face, face->frameInfo.channelId);
                faceMap->Clear(key);
            }
        }
    }
}

void QualityEvaluation::SendThread()
{
    while (!sendStop_) {
        // send data if exist time is greater than maxSendTime_
        SendData();
        std::this_thread::sleep_for(std::chrono::seconds(sleepTime_));
    }
}
} // namespace ascendFaceRecognition
